(module interp (lib "eopl.ss" "eopl")
  
  (require "drscheme-init.scm")

  (require "lang.scm")
  (require "data-structures.scm")
  (require "environments.scm")

  (provide value-of-program value-of)

;;;;;;;;;;;;;;;; the interpreter ;;;;;;;;;;;;;;;;

  ;; value-of-program : Program -> Expval
  ;; Page: 284
  (define value-of-program
    (lambda (pgm)
      (cases program pgm
        (a-program (module-defs body)
          (let ((env 
                  (add-module-defns-to-env module-defs (empty-env))))
            ;; (eopl:pretty-print env)
            (value-of body env))))))

  ;; add-module-defns-to-env : Listof(Defn) * Env -> Env
  ;; Page: 284
  (define add-module-defns-to-env
    (lambda (defs env)
      (if (null? defs)
        env
        (cases module-definition (car defs)
          (a-module-definition (m-name iface m-body)
            (add-module-defns-to-env
              (cdr defs)
              (extend-env-with-module
                m-name 
                (value-of-module-body m-body env)
                env)))))))

  ;; We will have let* scoping inside a module body.
  ;; We put all the values in the environment, not just the ones
  ;; that are in the interface.  But the typechecker will prevent
  ;; anybody from using the extras.

  ;; value-of-module-body : ModuleBody * Env -> TypedModule
  ;; Page: 285
  (define value-of-module-body
    (lambda (m-body env)    
      (cases module-body m-body
        (defns-module-body (defns)
          (simple-module
            (defns-to-env defns env))) )))


  (define raise-cant-apply-non-proc-module!
    (lambda (rator-val)
      (eopl:error 'value-of-module-body 
        "can't apply non-proc-module-value ~s" rator-val)))

  ;; defns-to-env : Listof(Defn) * Env -> Env
  ;; Page: 285
  (define defns-to-env
    (lambda (defns env)
      (if (null? defns)
        (empty-env)                ; we're making a little environment
        (cases definition (car defns)
          (val-defn (var exp)
            (let ((val (value-of exp env)))
              ;; new environment for subsequent definitions
              (let ((new-env (extend-env var val env)))
                (extend-env var val
                  (defns-to-env
                    (cdr defns) new-env)))))
          ))))

  ;; value-of : Exp * Env -> ExpVal
  (define value-of
    (lambda (exp env)

      (cases expression exp

        (const-exp (num) (num-val num))

        (var-exp (var) (apply-env env var))

        (qualified-var-exp (m-name var-name)
          (lookup-qualified-var-in-env m-name var-name env))

        (diff-exp (exp1 exp2)
          (let ((val1
		  (expval->num
		    (value-of exp1 env)))
                (val2
		  (expval->num
		    (value-of exp2 env))))
            (num-val
	      (- val1 val2))))
        
        (zero?-exp (exp1)
	  (let ((val1 (expval->num (value-of exp1 env))))
	    (if (zero? val1)
	      (bool-val #t)
	      (bool-val #f))))

        (if-exp (exp0 exp1 exp2) 
          (if (expval->bool (value-of exp0 env))
            (value-of exp1 env)
            (value-of exp2 env)))

        (let-exp (var exp1 body)       
          (let ((val (value-of exp1 env)))
            (let ((new-env (extend-env var val env)))
              ;; (eopl:pretty-print new-env)
              (value-of body new-env))))

        (proc-exp (bvar ty body)
	  (proc-val
	    (procedure bvar body env)))

        (call-exp (rator rand)          
          (let ((proc (expval->proc (value-of rator env)))
                (arg  (value-of rand env)))
	    (apply-procedure proc arg)))

        (letrec-exp (ty1 proc-name bvar ty2 proc-body letrec-body)
          (value-of letrec-body
            (extend-env-recursively proc-name bvar proc-body env)))

        )))

  ;; apply-procedure : Proc * ExpVal -> ExpVal
  (define apply-procedure
    (lambda (proc1 arg)
      (cases proc proc1
        (procedure (var body saved-env)
          (value-of body (extend-env var arg saved-env))))))

  )
  


  
